#include <sstream>
#include <assert.h>
#include <iostream>  
#include <stdlib.h>  
#include <stdio.h>  
#include <string.h> 
#include <fstream>
#include <sstream> 

#include "IOOpenFOAM.h"

bool isNumber(const string_t &str)
{
	std::stringstream ss(str);
	double d;
	if (!(ss >> d))
	{		
		return false;
	}	
	return true;
}

bool readExpectingChar( std::ifstream &infile, const char expected, 
						string_t &stringRead= string_t(""))
{
	stringRead.clear();
	char chaGet;
	while (!infile.eof())
	{
		infile.get(chaGet);
		if (chaGet == expected) {
			return true;
		}
		stringRead.push_back(chaGet);
	}
	return false;
}

bool readExpectingString(std::ifstream &infile, const string_t expected)
{
	string_t strGet;
	while (!infile.eof())
	{
		infile >> strGet;
		if (strGet == expected) {
			return true;
		}
	}
	return false;
}

string_t readNextString(std::ifstream &infile)
{
	string_t strGet;
	while (!infile.eof())
	{
		infile >> strGet;
		return strGet;
	}
	return "";
}

string_t readNextNumber(std::ifstream &infile)
{
	string_t strGet;
	while (!infile.eof())
	{
		infile >> strGet;
		if(isNumber(strGet)) return strGet;
	}
	return "";
}

string_t readValue(std::ifstream &infile)
{
	string_t strRead;
	readExpectingChar(infile, ';', strRead);

	strRead.erase(0, strRead.find_first_not_of(" "));
	strRead.erase(strRead.find_last_not_of(" ") + 1);
	
	return string_t(strRead);
}

void splitString(string_t &str, svector &strvec)
{
	using namespace std;

	strvec.clear();
	istringstream is(str);
	string tmp;
	while (is >> tmp)
	{
		strvec.push_back(tmp);
	}
}

void readFoamHeader(std::ifstream &infile, FoamHeader &header)
{
	readExpectingString(infile, "FoamFile");
	readExpectingString(infile, "{");

	while (!infile.eof())
	{
		string_t strGet = readNextString(infile);
		if (strGet == "version")       header.m_version  = readValue(infile);
		else if (strGet == "format")   header.m_format   = readValue(infile);
		else if (strGet == "class")    header.m_class    = readValue(infile);
		else if (strGet == "location") header.m_location = readValue(infile);
		else if (strGet == "object")   header.m_object   = readValue(infile);
		else if (strGet == "arch") {
			header.m_arch = readNextString(infile);
			sscanf(header.m_arch.c_str(),"\"LSB;label=%d;scalar=%d\"", 
				&header.label, &header.scalar);
		}
		else if (strGet == "}") break;		
	}	
}

void read_points(string_t fileName, int &nNodes, KNodePtr &nodes)
{
	using namespace std;	
	ifstream infile(fileName.c_str(), ios::binary);
	if (!infile) {
		throw string_t("\nERROR : failed to open file '") + fileName + ".";
	}

	//
	FoamHeader header;
	readFoamHeader(infile, header);	

	// number of nodes
	nNodes = atoi(readNextNumber(infile).c_str());
	if (nodes != nullptr) delete[]nodes;
	nodes = new KNode[nNodes];
	for (int n = 0; n < nNodes; n++) nodes[n].index = n;

	readExpectingChar(infile, '(');

	//
	// read points
	if (header.m_format == "binary") {		

		// read all data
		if (header.scalar == 64) { // "LSB;label=32;scalar=64"
			double *coord = new double[nNodes * 3];
			infile.read(reinterpret_cast<char *>(coord), sizeof(double)*nNodes*3);
			for (int i = 0; i < nNodes; i++) {
				nodes[i].coord.x = coord[i * 3 + 0];
				nodes[i].coord.y = coord[i * 3 + 1];
				nodes[i].coord.z = coord[i * 3 + 2];
			}
			delete[] coord;
		}
		else {
			throw string_t("\nERROR: unsupported 'arch  scalar=") 
				+ std::to_string(header.scalar) + "', in file : " + fileName;
		}

		string_t strGet;
		readExpectingChar(infile, ')', strGet);
	}
	else
	{	
		int count = 0;
		while (!infile.eof())
		{
			string_t strGet;
			readExpectingChar(infile, '(', strGet);
			readExpectingChar(infile, ')', strGet);

			svector stringVector;
			splitString(strGet, stringVector);

			if (stringVector.size() == 3) {
				nodes[count].coord.x = atof(stringVector[0].c_str());
				nodes[count].coord.y = atof(stringVector[1].c_str());
				nodes[count].coord.z = atof(stringVector[2].c_str());
				count++;
			}

			if (count == nNodes) {
				if (readNextString(infile) == ")") break;
				else throw string_t("\nERROR : read 'points'.");
			}
		}		
	}	

	infile.close();
}

void read_faces(string_t fileName, int &nFaces, KFacePtr &faces, const KNodePtr &nodes)
{
	using namespace std;
	ifstream infile(fileName.c_str(), ios::binary);
	if (!infile) {
		throw string_t("\nERROR : failed to open file '") + fileName + ".";
	}

	//
	FoamHeader header;
	readFoamHeader(infile, header);	

	// number of nodes
	nFaces = atoi(readNextNumber(infile).c_str());
	if (faces != nullptr) delete[]faces;	

	//
	// read faces
	if (header.m_format == "binary") {
		//
		ivector *face2node = nullptr;
		//
		if (header.m_class == "faceCompactList") {			
			//
			face2node = new ivector[nFaces - 1];
			//
			readExpectingChar(infile, '(');
			//		
			int bodySizeCal = 0;
			if (header.label == 32) { // int 32
				int *faceIndex = new int[nFaces];
				infile.read(reinterpret_cast<char *>(faceIndex), sizeof(int)*nFaces);
				//
				for (int i = 0; i < nFaces - 1; i++) {
					int tmpInt = faceIndex[i + 1] - faceIndex[i];
					face2node[i].resize(tmpInt);
					bodySizeCal += tmpInt;
				}
				//
				delete[]faceIndex;
			}
			else if (header.label == 64) { // int 64
				long long int *faceIndex = new long long int[nFaces];
				infile.read(reinterpret_cast<char *>(faceIndex), sizeof(long long int)*nFaces);
				//
				for (int i = 0; i < nFaces - 1; i++) {
					int tmpInt = faceIndex[i + 1] - faceIndex[i];
					face2node[i].resize(tmpInt);
					bodySizeCal += tmpInt;
				}
				//
				delete[]faceIndex;
			}
			else {
				throw string_t("\nERROR : read 'faces' : label = ") + std::to_string(header.label);
			}

			//
			if (!readExpectingChar(infile, ')')) return;

			nFaces--;

			int bodySize = atoi(readNextNumber(infile).c_str());
			assert(bodySize == bodySizeCal);

			readExpectingChar(infile, '(');

			if (header.label == 32) {
				int *faceBody = new int[bodySize];
				infile.read(reinterpret_cast<char *>(faceBody), sizeof(int)*bodySize);
				//
				int count = 0;
				for (int i = 0; i < nFaces; i++) {
					for (int j = 0; j < face2node[i].size(); j++) {
						face2node[i][j] = faceBody[count++];
					}
				}				
			}
			else if (header.label == 64) {
				long long int *faceBody = new long long int[bodySize];
				infile.read(reinterpret_cast<char *>(faceBody), sizeof(long long int)*bodySize);
				//
				int count = 0;
				for (int i = 0; i < nFaces; i++) {
					for (int j = 0; j < face2node[i].size(); j++) {
						face2node[i][j] = faceBody[count++];
					}
				}
			}
		}
		else if (header.m_class == "faceList") {
			//
			face2node = new ivector[nFaces];
			//
			readExpectingChar(infile, '(');
			//
			ivector faceBody32;
			std::vector<long long int> faceBody64;
			//
			string beforStr;
			for (int f = 0; f < nFaces; f++) {
				//
				readExpectingChar(infile, '(', beforStr);
				int nNode = atoi(beforStr.c_str());				
				// 
				if (header.label == 32) {
					faceBody32.resize(nNode);
					infile.read(reinterpret_cast<char *>(&faceBody32[0]), sizeof(int)*nNode);
					for(int i=0; i<nNode; i++) face2node[f].push_back(faceBody32[i]);
				}
				else if (header.label == 64) {
					faceBody64.resize(nNode);
					infile.read(reinterpret_cast<char *>(&faceBody64[0]), sizeof(long long int)*nNode);
					for (int i = 0; i<nNode; i++) face2node[f].push_back(faceBody64[i]);
				}
				else {
					throw string_t("\nERROR : read 'faces' : label = ") + std::to_string(header.label);
				}

				readExpectingChar(infile, ')');
			}		

			//
			readExpectingChar(infile, ')');
		}

		//
		faces = new KFace[nFaces];
		for (int i = 0; i < nFaces; i++) {
			faces[i].index = i;
			faces[i].nNodes = face2node[i].size();
			faces[i].nodes  = new KNodePtr[faces[i].nNodes];
			for (int j = 0; j < face2node[i].size(); j++) {
				faces[i].nodes[j] = nodes + face2node[i][j];
			}
		}

		//
		delete[]face2node;
	}
	else {
		faces = new KFace[nFaces];
		for (int i = 0; i < nFaces; i++) faces[i].index = i;

		readExpectingChar(infile, '(');

		int count = 0;
		while (!infile.eof())
		{
			string_t strGet;
			readExpectingChar(infile, '(', strGet);
			int nNodeofFace = atoi(strGet.c_str());

			readExpectingChar(infile, ')', strGet);

			svector stringVector;
			splitString(strGet, stringVector);

			if (nNodeofFace != stringVector.size()) {
				throw string_t("\nERROR : read 'faces' : size of face node mismatched");
			}

			faces[count].nNodes = nNodeofFace;
			faces[count].nodes = new KNodePtr[nNodeofFace];
			for (int i = 0; i < nNodeofFace; i++) {
				int nodeID = atoi(stringVector[i].c_str());
				faces[count].nodes[i] = nodes + nodeID;
			}

			count++;

			if (count == nFaces) {
				if (readNextString(infile) == ")") break;
				else throw string_t("\nERROR : read 'faces'.");
			}
		}

		assert(count == nFaces);
	}

	infile.close();
}

void read_owner_neighbour(string_t fileName, int &nFaces, int *facecell, int &nCells)
{
	using namespace std;
	ifstream infile(fileName.c_str(), ios::binary);
	if (!infile) {
		throw string_t("\nERROR : failed to open file '") + fileName + ".";
	}

	//
	FoamHeader header;
	readFoamHeader(infile, header);	

	// number
	nFaces = atoi(readNextNumber(infile).c_str());	

	//
	readExpectingChar(infile, '(');

	//
	// read face cells
	if (header.m_format == "binary") {		
		//
		if (header.label == 32) {	
			int *faceEle = new int[nFaces];
			infile.read(reinterpret_cast<char *>(faceEle), sizeof(int)*nFaces);
			for (int n = 0; n < nFaces; n++) {
				facecell[n] = faceEle[n];
				nCells = MAX(nCells, facecell[n]);
			}
		}
		else if (header.label == 64) {
			long long int *faceEle = new long long int[nFaces];
			infile.read(reinterpret_cast<char *>(faceEle), sizeof(long long int)*nFaces);
			for (int n = 0; n < nFaces; n++) {
				facecell[n] = faceEle[n];
				nCells = MAX(nCells, facecell[n]);
			}
		}
		else {
			throw string_t("\nERROR : read 'owner/neighbour' : label = ") + std::to_string(header.label);
		}
		//
		readExpectingChar(infile, ')');
	}
	else {
		int count = 0;
		while (!infile.eof())
		{
			int cellID = atoi(readNextString(infile).c_str());
			facecell[count] = cellID;
			//
			nCells = MAX(nCells, cellID);
			//
			count++;
			if (count == nFaces) break;
		}
		//
		readExpectingChar(infile, ')');
	}

	infile.close();
}

void read_boudary(string_t fileName, KFacePtr &faces, std::vector<KBoundaryShaPtr> &boundaries)
{
	using namespace std;
	ifstream infile(fileName.c_str(), ios::binary);
	if (!infile) {
		throw string_t("\nERROR : failed to open file '") + fileName + ".";
	}

	//
	FoamHeader header;
	readFoamHeader(infile, header);	

	// number of boundaries
	int_t nBoundary = atoi(readNextNumber(infile).c_str());
	boundaries.resize(nBoundary);

	//
	readExpectingChar(infile, '(');

	//
	int count = 0;
	while (!infile.eof())
	{
		string_t bcName = readNextString(infile);
		if (bcName == ")") break;

		int startFace = 0; int nFaces = 0;
		string_t typeString;

		readExpectingChar(infile, '{');
		while (!infile.eof())
		{
			string_t dict = readNextString(infile);
			if (dict == "type") {
				string_t value;
				readExpectingChar(infile, ';', value);
				value.erase(0, value.find_first_not_of(" "));
				value.erase(value.find_last_not_of(" ") + 1);
				typeString = value;				
			}
			else if (dict == "nFaces") {
				string_t value;
				readExpectingChar(infile, ';', value);
				nFaces = atoi(value.c_str());
			}
			else if (dict == "startFace") {
				string_t value;
				readExpectingChar(infile, ';', value);
				startFace = atoi(value.c_str());
			}
			else if (dict == "}") {
				break;
			}			
		}

		//
		boundaries[count] = std::make_shared<KBoundary>();
		boundaries[count]->setTypeByName(typeString);
		boundaries[count]->name   = bcName;
		boundaries[count]->faces.swap(std::vector<KFacePtr>(nFaces, nullptr));
		for (int f = 0; f < nFaces; f++) {
			//
			boundaries[count]->faces[f] = faces + (f+ startFace);
			//
			faces[f + startFace].boundary = boundaries[count];
		}

		count++;
	}

	infile.close();
}

